package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"strconv"

	"reflect"
	"strings"
)

type MysqlConfig struct {
	Address  string `ini:"地址"`
	Port     int    `ini:"port"`
	Password string `ini:"password"`
	Username string `ini:"username"`
}

type RedisConfig struct {
	Host     string `ini:"host"`
	Port     int    `ini:"port"`
	Password string `ini:"password"`
	Database int    `ini:"database"`
	Test     bool   `ini:"测试"`
}

type Config struct {
	MysqlConfig `ini:"mysql"`
	RedisConfig `ini:"redis"`
}

func loadIni(fn string, data interface{}) (err error) {
	//0.参数校验
	//0.1传进来的data参数必须是指针，因为只有指针才能在函数中被修改
	t := reflect.TypeOf(data) //获取类型
	v := reflect.ValueOf(data)
	if t.Kind() != reflect.Ptr { //获取种类并判断是否为指针
		err = errors.New("传入参数不是指针")
		return
	}
	//0.2传进来的data参数必须是结构体类型指针
	if t.Elem().Kind() != reflect.Struct {
		err = errors.New("data param should be a struct pointer")
		return
	}
	//1.读文件得到每行数据
	buf, err := ioutil.ReadFile(fn)
	if err != nil {
		fmt.Printf("文件读取错误%v\n", err)
		return
	}
	lineSlice := strings.Split(string(buf), "\r\n")
	fmt.Println(lineSlice)
	var structName string
	for idx, line := range lineSlice {
		line = strings.TrimSpace(line) //去掉每行字符串的空格
		//1.1如果是注释就跳过
		if strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") {
			continue
		}
		//如果是空行就跳过
		if len(line) == 0 {
			continue
		}
		if strings.HasPrefix(line, "[") {
			//1.2如果是[开头就表示是节（section）
			//1.2.1判断时候以]结束
			if line[len(line)-1] != ']' {
				err = fmt.Errorf("line:%d syntax error", idx+1)
				return
			}
			setionName := strings.TrimSpace(line[1 : len(line)-1])
			if len(setionName) == 0 {
				err = fmt.Errorf("line:%d syntax error", idx+1)
				return
			}

			//根据字符串sectionName去data里面根据反射找到对应的结构体
			for i := 0; i < t.Elem().NumField(); i++ {

				field := t.Elem().Field(i)
				if setionName == field.Tag.Get("ini") {
					structName = field.Name
					fmt.Println(setionName, structName)
				}
			}
		} else {
			//2.如果不是[开头就是=分割的键值对
			//2.1以等号分割这一行,等号左边是key,右边是value
			if strings.Index(line, "=") == -1 || strings.HasPrefix(line, "=") {
				err = fmt.Errorf("line:%d syntax error", idx+1)
				return
			}
			index := strings.Index(line, "=") //获取第一个以子串分割的字符串索引号
			key := strings.TrimSpace(line[:index])
			value := strings.TrimSpace(line[index+1:])
			//2.2根据structName去data中获取相应的结构体

			sValue := v.Elem().FieldByName(structName) //拿到嵌套结构体的值信息
			sType := sValue.Type()                     //拿到嵌套结构体的类型信息
			if sType.Kind() != reflect.Struct {
				err = fmt.Errorf("data中的%s字段应该是个结构体", err)
				return
			}
			var fieldName string
			//2.3遍历嵌套结构体的每一个字段,判断tag是不是等于key
			for i := 0; i < sValue.NumField(); i++ {
				field := sType.Field(i)
				if field.Tag.Get("ini") == key {
					fieldName = field.Name
					//fmt.Printf(fieldName)
				}
			}
			if len(fieldName) == 0 {
				continue
			}
			//2.4如果key=tag,给这个字段赋值
			//2.4.1根据fieldName取出这个字段
			fileObj := sValue.FieldByName(fieldName)
			//fmt.Println(fieldName, fileObj.Kind())
			kind := fileObj.Kind()
			switch kind {
			case reflect.String:
				fileObj.SetString(value)
			case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8:
				var valueint int64
				valueint, err = strconv.ParseInt(value, 10, 64)
				if err != nil {
					fmt.Println("非数字格式:", err)
					return
				}
				fileObj.SetInt(valueint)
			case reflect.Bool:
				var valueBool bool
				valueBool, err = strconv.ParseBool(value)
				if err != nil {
					fmt.Println("非布尔类型:", err)
					return
				}
				fileObj.SetBool(valueBool)
			}
		}
	}
	return

}

func main() {
	var cgf Config
	//var mc MysqlConfig
	err := loadIni("./conf.ini", &cgf)
	if err != nil {
		fmt.Println("get ini file error:", err)
		return
	}
	ct := reflect.ValueOf(cgf)
	cv := reflect.TypeOf(cgf)
	for i := 0; i < ct.NumField(); i++ {
		for j := 0; j < ct.Field(i).NumField(); j++ {
			fmt.Println(cv.Field(i).Type.Field(j).Name, ct.Field(i).Field(j))
		}
	}
}
